/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: vos_mem_common.c
 *
 * Purpose: memory operation common interface
 *
 * Developer:
 *   wen.gu , 2016-5-09
 *
 * TODO: 
 *
 ***************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


//API header file
#include "vos_mem.h"

#if defined(MEM_DEBUG)
#include "thread/vos_mutex.h"
#include "utils/vos_list.h"
#include "vos_error.h"
#include "general_error.h"


#undef memAlloc
#undef memRealloc
#undef memFree
/***************************************************************************
 *
 * macro define
 *
 ***************************************************************************/
#define MEM_FUNC_NAME_LEN 31

#define LOGD printf
#define LOGE printf

#define VOS_MEM_DEBUG_MUTEX        g_env_mutex_lock
#define vosGlobalMutexLock(lock)   vosMutexLock(lock)
#define vosGlobalMutexUnlock(lock) vosMutexUnlock(lock)
/***************************************************************************
 *
 * data structure define
 *
 ***************************************************************************/
typedef struct my_mem_item_s 
{    
    GS32 line; /** do malloc at line in file */
    GU32 size;
    GPTR addr;
    vosListHead_t list;
    GS08 func[MEM_FUNC_NAME_LEN + 1];
}my_mem_item_t;


/***************************************************************************
 *
 * virtual memory API define for debug
 *
 ***************************************************************************/

static my_mem_item_t g_mem_info_header = {0};
static GBOL g_is_env_initialized = GFALSE;
static GPHD g_env_mutex_lock = NULL;

static GS32 mem_item_add(my_mem_item_t* item)
{
    vosGlobalMutexLock(VOS_MEM_DEBUG_MUTEX);
    vosListHead_t* list = &(g_mem_info_header.list);
    if ((!list->next) && (!list->prev))
    {
        INIT_LIST_HEAD(list);
    }    
    
    list_add_tail(&item->list, list);
    vosGlobalMutexUnlock(VOS_MEM_DEBUG_MUTEX);
    return G_OK;
}


static GS32 MemInfoAdd_l(GPTR addr, GU32 size, const char* func_name, GS32 line_num)
{
    my_mem_item_t* item = (my_mem_item_t*)malloc(sizeof(my_mem_item_t));

    if (item)
    {
        GS32 len = strlen(func_name);
        len = GMIN(len, MEM_FUNC_NAME_LEN);
        memset(item, 0, sizeof(item));
        item->addr = addr;
        item->size = size;
        item->line = line_num;

        memcpy(item->func, func_name, len);

        mem_item_add(item);

        return G_OK;
    }

    return G_FAIL;
}

static GS32 MemInfoDel_l(GPTR addr, const char* func_name, GS32 line_num)
{
    GS32 ret = G_FAIL;
    vosGlobalMutexLock(VOS_MEM_DEBUG_MUTEX);
    vosListHead_t* list = &(g_mem_info_header.list);
    
    while (!list_empty(list))
    {
        my_mem_item_t* item = list_first_entry(list, my_mem_item_t, list);

        if (item->addr == addr)
        {
            list_del(&item->list);
            free(item);
            ret = G_OK;
            break;
        }
    }

    vosGlobalMutexUnlock(VOS_MEM_DEBUG_MUTEX);

    return G_FAIL;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

void *memAllocDebug(GU32 size, const char* func_name, GS32 line_num)
{
    void* mem = NULL;

    if (g_is_env_initialized == GTRUE)
{
        mem = memAlloc(size);

    if (mem)
    {
        MemInfoAdd_l(mem, size, func_name, line_num);
    }
    }


    return mem;
}

void *memReallocDebug(void* oldMem, GU32 newSize,
                      const char* func_name, GS32 line_num)
{
    void* mem = NULL;
    if (g_is_env_initialized == GTRUE)
    {
    if (oldMem)
    {
        MemInfoDel_l(oldMem, func_name, line_num);
    }

        mem = memRealloc(oldMem, newSize);

    if (mem)
    {
        MemInfoAdd_l(mem, newSize, func_name, line_num);
    }
    }

    return mem;
}


void memFreeDebug(void* mem, const char* func_name, GS32 line_num)
{
    if (g_is_env_initialized == GTRUE)
    {
    if (mem)
    {
        MemInfoDel_l(mem, func_name, line_num);
    }

    memFree(mem);
}
}


GS32 memDebugInit()
{
    GS32 ret = G_ErrorInvalidOperation;

    if (g_is_env_initialized == GFALSE)
    {
        g_env_mutex_lock = vosMutexCreate();

        if (g_env_mutex_lock)
{
            g_is_env_initialized = GTRUE;
            ret = G_OK;
        }
        else
        {
            LOGE("create mutex lock instance failed\n");
            ret = G_ErrorInsufficientResources;
        }
    }

    return ret;
}


void memDebugUninit()
{
    if (g_is_env_initialized == GTRUE)
    {
        vosMutexLock(g_env_mutex_lock);
    vosListHead_t* list = &(g_mem_info_header.list);

    if (!list_empty(list)) //have memory leak,
    {
        LOGD("have some memory doesn't free, it's maybe a bug of memory leak\n");
        while (!list_empty(list))
        {
            my_mem_item_t* item = list_first_entry(list, my_mem_item_t, list);

            LOGD("memory-addr:%p, size:%d, where malloc:%s, line number: %d\n",
                item->addr, item->size, item->func, item->line);
            list_del(&item->list);
            free(item);
        }
    }
        vosMutexUnlock(g_env_mutex_lock);

        vosMutexFree(g_env_mutex_lock);
        g_env_mutex_lock = NULL;
        g_is_env_initialized = GFALSE;
    }
}

#endif
